﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace Shen.NET
{
    public abstract class Expr
    {
        protected Expr(Token token, bool tail)
        {
            Token = token;
            IsTail = tail;
        }

        public Token Token { get; private set; }
        public bool IsTail { get; private set; }

        public Object Eval(Context context, bool runRedirects = true)
        {
            var result = Eval0(context);

            while (runRedirects && (result is Redirect))
                result = result.As<Redirect>().Go();

            return result;
        }

        protected abstract Object Eval0(Context context);

        protected Object EvalR(Expr expr, Context context)
        {
            return IsTail ? new Redirect(() => expr.Eval(context, false)) : expr.Eval(context);
        }

        protected Object EvalL(IFunction f, Object[] args)
        {
            return IsTail ? new Redirect(() => f.Apply(args, false)) : f.Apply(args, true);
        }

        protected Object EvalF(Func<Object> func)
        {
            return IsTail ? new Redirect(func) : func();
        }
    }
    
    public abstract class AtomExpr<T> : Expr
    {
        protected AtomExpr(T val, Token token, bool tail, Func<Context, Object> eval)
            : base(token, tail)
        {
            Value = val;
            _eval = eval;
        }

        public T Value { get; private set; }
        private readonly Func<Context, Object> _eval;

        protected override object Eval0(Context context)
        {
            return _eval(context);
        }

    }

    public class BooleanExpr : AtomExpr<Boolean>
    {
        public BooleanExpr(bool val, Token token, bool tail) : base(val, token, tail, _ => val)
        {
        }
    }

    public class NumberExpr : AtomExpr<Decimal>
    {
        public NumberExpr(decimal val, Token token, bool tail) : base(val, token, tail, _ => val)
        {
        }
    }

    public class StringExpr : AtomExpr<String>
    {
        public StringExpr(String val, Token token, bool tail) : base(val, token, tail, _ => val)
        {
        }
    }

    public class SymbolExpr : AtomExpr<String>
    {
        public SymbolExpr(String id, Token token, bool tail) : base(id, token, tail, c => c.Resolve(id))
        {
        }

        public String Id { get { return Value; } }
    }

    public class EmptyExpr : AtomExpr<Cons>
    {
        public EmptyExpr(Token token, bool tail) : base(Cons.EmptyList, token, tail, _ => Cons.EmptyList)
        {
        }
    }

    public class LetExpr : Expr
    {
        public LetExpr(SymbolExpr symbol, Expr def, Expr body, Token token, bool tail) : base(token, tail)
        {
            Symbol = symbol;
            Definition = def;
            Body = body;
        }

	    public SymbolExpr Symbol { get; private set; }
	    public Expr Definition { get; private set; }
	    public Expr Body { get; private set; }

        protected override object Eval0(Context context)
        {
            return EvalR(Body, context.With(Symbol.Id, Definition.Eval(context)));
        }
    }

    public class LambdaExpr : Expr
    {
        public LambdaExpr(SymbolExpr param, Expr body, Token token, bool tail) : base(token, tail)
        {
            Param = param;
            Body = body;
        }

	    public SymbolExpr Param { get; private set; }
	    public Expr Body { get; private set; }

        protected override object Eval0(Context context)
        {
            return new Defun(new List<SymbolExpr> {Param}, Body, context);
        }
    }

    public class DefunExpr : Expr
    {
        public DefunExpr(SymbolExpr name, List<SymbolExpr> paramz, Expr body, Token token, bool tail) : base(token, tail)
        {
            Name = name;
            Params = paramz;
            Body = body;
        }

	    public SymbolExpr Name { get; private set; }
	    public List<SymbolExpr> Params { get; private set; }
	    public Expr Body { get; private set; }

        protected override object Eval0(Context context)
        {
            var sym = context.Intern(Name.Id);
            sym.Value = new Defun(this, context);
            return sym;
        }
    }

    public class IfExpr : Expr
    {
        public IfExpr(Expr condition, Expr consequent, Expr alternative, Token token, bool tail) : base(token, tail)
        {
            Condition = condition;
            Consequent = consequent;
            Alternative = alternative;
        }

        public Expr Condition { get; private set; }
        public Expr Consequent { get; private set; }
        public Expr Alternative { get; private set; }

        protected override object Eval0(Context context)
        {
            return EvalR(Condition.Eval(context).As<Boolean>() ? Consequent : Alternative, context);
        }
    }

    public class CondExpr : Expr
    {
        public CondExpr(List<Tuple<Expr, Expr>> clauses, Token token, bool tail) : base(token, tail)
        {
            Clauses = clauses;
        }

        public List<Tuple<Expr, Expr>> Clauses { get; private set; }

        protected override object Eval0(Context context)
        {
            foreach (var clause in Clauses)
                if (clause.Item1.Eval(context).As<Boolean>())
                    return EvalR(clause.Item2, context);

            throw new ApplicationException("No clause was true");
        }
    }
    
    public class AndExpr : Expr
    {
        public AndExpr(Expr first, Expr second, Token token, bool tail) : base(token, tail)
        {
            First = first;
            Second = second;
        }

        public Expr First { get; private set; }
        public Expr Second { get; private set; }

        protected override object Eval0(Context context)
        {
            return First.Eval(context).As<Boolean>() && Second.Eval(context).As<Boolean>();
        }
    }
    
    public class OrExpr : Expr
    {
        public OrExpr(Expr first, Expr second, Token token, bool tail) : base(token, tail)
        {
            First = first;
            Second = second;
        }

        public Expr First { get; private set; }
        public Expr Second { get; private set; }

        protected override object Eval0(Context context)
        {
            return First.Eval(context).As<Boolean>() || Second.Eval(context).As<Boolean>();
        }
    }

    public class FreezeExpr : Expr
    {
        public FreezeExpr(Expr body, Token token, bool tail) : base(token, tail)
        {
            Body = body;
        }

        public Expr Body { get; private set; }

        protected override object Eval0(Context context)
        {
            return new Defun(new List<SymbolExpr>(), Body, context);
        }
    }

    public class AppExpr : Expr
    {
        public AppExpr(Expr functor, List<Expr> args, Token token, bool tail) : base(token, tail)
        {
            Functor = functor;
            Args = args;
        }

        public Expr Functor { get; private set; }
	    public List<Expr> Args { get; private set; }
        
        protected override object Eval0(Context context)
        {
            return EvalL(Functor.Eval(context).As<IFunction>(), Args.Select(x => x.Eval(context)).ToArray());
        }
    }

    public class TrapExpr : Expr
    {
        public TrapExpr(Expr body, Expr handler, Token token, bool tail) : base(token, tail)
        {
            Body = body;
            Handler = handler;
        }

        public Expr Body { get; private set; }
        public Expr Handler { get; private set; }

        protected override object Eval0(Context context)
        {
            try
            {
                return Body.Eval(context);
            }
            catch (Exception exc)
            {
                return EvalL(Handler.Eval(context).As<IFunction>(), new object[] {new Error(exc)});
            }
        }
    }
}
